package jps_s10996_s7175.objects;

import java.io.File;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.TreeMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import edu.pjwstk.jps.datastore.IOID;
import edu.pjwstk.jps.datastore.ISBAObject;
import edu.pjwstk.jps.datastore.ISBAStore;

public class SBAStore implements ISBAStore {

    private int id = 0;
    private final TreeMap<DBID, SBAObject> store;

    public SBAStore() {
        store = new TreeMap<DBID, SBAObject>();
    }

    @Override
    public ISBAObject retrieve(IOID oid) {
        return store.get(oid);
    }

    public boolean hasElementAChild(IOID elem, IOID child) {
        boolean result = false;
        DBID lookingFor = (DBID) child;

        SBAObject parent = (SBAObject) retrieve(elem);
        if (parent instanceof ComplexObject) {
            ComplexObject c = (ComplexObject) parent;
            for (IOID innerI : c.getChildOIDs()) {
                if ((DBID) innerI == (DBID) child) {
                    result = true;
                    break;
                }
            }
        }
        return result;
    }

    @Override
    public IOID getEntryOID() {
        return store.isEmpty() ? null : store.firstKey();
    }

    @Override
    public void loadXML(String filePath) {
        try {
            File fXmlFile = new File(filePath);
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(fXmlFile);
            doc.getDocumentElement().normalize();

            walkNode(doc.getDocumentElement(), getEntryOID());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public TreeMap<DBID, SBAObject> getElements() {
        return this.store;
    }

    @SuppressWarnings({})
    private void walkNode(Element e, IOID parentID) {
        DBID nID = (DBID) generateUniqueOID();
        if (e.getChildNodes().getLength() > 1) {
            // complex object
            String name = e.getNodeName();
            store.put(nID, new ComplexObject(name, nID, null));
            for (int temp = 0; temp < e.getChildNodes().getLength(); temp++) {
                Node nNode = e.getChildNodes().item(temp);
                if (nNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element eElement = (Element) nNode;
                    walkNode(eElement, nID);
                }
            }
        } else {
            // simple object
            /*String name = e.getNodeName();
             String value = e.getChildNodes().item(0).getNodeValue();
             Object o = getValueOfString(value);
             store.put(nID, new StringObject(name, (DBID) nID, value));*/
            // update parent now
            String name = e.getNodeName();
            Object o = e.getChildNodes().item(0).getNodeValue();
            boolean bNeedNextTry = true;
            try {
                if (bNeedNextTry) {
                    store.put(nID, new IntegerObject(name, nID, new Integer((String) o)));
                    bNeedNextTry = false;
                }
            } catch (Exception ex) {
            }

            try {
                if (bNeedNextTry) {
                    store.put(nID, new DoubleObject(name, nID, new Double((String) o)));
                    bNeedNextTry = false;
                }
            } catch (Exception ex) {
            }

            try {
                if (bNeedNextTry) {
                    String tmp = (String) o;
                    if (tmp.toLowerCase().equals("false") || tmp.toLowerCase().equals("true")) {
                        store.put(nID, new BooleanObject(name, nID, new Boolean((String) o)));
                        //return;
                    } else {
                        store.put(nID, new StringObject(name, nID, (String) o));
                    }
                    bNeedNextTry = false;
                }
                // update parent now
            } catch (Exception ex) {
            }
        }
        if (parentID != null) {
            ((ComplexObject) retrieve(parentID)).add(nID);
        }

    }

    @Override
    public IOID generateUniqueOID() {
        int i = 0;
        synchronized (SBAStore.class) {
            i = this.id;
            this.id++;
        }
        return new DBID(i);
    }

    public static boolean isSimpleType(Object o) {
        /*
         * System.out.println("checking class of " + o.getClass());
         * System.out.println("\twith " + String.class);
         * System.out.println("\twith " + Integer.class);
         * System.out.println("\twith " + Double.class);
         * System.out.println("\twith " + Boolean.class);
         */
        if (o.getClass() == Integer.class || o.getClass() == int.class
                || o.getClass() == String.class || o.getClass() == Double.class
                || o.getClass() == double.class
                || o.getClass() == Boolean.class
                || o.getClass() == boolean.class) {
            return true;
        } else {
            return false;
        }
    }

    public static Object getValueOfString(String s) {
        Scanner sc = new Scanner(s);
        return sc.hasNextInt() ? sc.nextInt() : sc.hasNextLong() ? sc
                .nextLong() : sc.hasNextDouble() ? sc.nextDouble() : sc
                .hasNext() ? sc.next() : s;
    }

    @Override
    public void addJavaObject(Object o, String objectName) {
        Class c = o.getClass();
        DBID newId = (DBID) generateUniqueOID();

        try {
            store.put(newId, new IntegerObject(objectName, newId, new Integer((String) o)));
            return;
        } catch (Exception ex) {
        }

        try {
            store.put(newId, new DoubleObject(objectName, newId, new Double((String) o)));
            return;
        } catch (Exception ex) {
        }

        try {
            String tmp = (String) o;
            if (tmp.toLowerCase().equals("false") || tmp.toLowerCase().equals("true")) {
                store.put(newId, new BooleanObject(objectName, newId, new Boolean((String) o)));
                return;
            } else {
                store.put(newId, new StringObject(objectName, newId, (String) o));
            }
        } catch (Exception ex) {
        }

        // System.out.println(objectName + " is not a very simple object: "
        // + c);
        for (Field f : o.getClass().getFields()) {
            // System.out.println("\t" + objectName + " has field: " +
            // f.getType());
            try {
                if (isSimpleType(f.get(o))) {
                    addJavaObject(f.get(o), f.getName());
                    // System.out.println(" \t it is");
                } else {
                    // System.out.println(" \t it is not");
                    // cannot be instanceof - because we do not know class
                    // type yet..
                    // it will be binded dynamicaly
                    if (Collection.class.isAssignableFrom(f.getType())) {
                        LinkedList<Object> list = (LinkedList) f.get(o);
                        addJavaCollection(list, f.getName());
                    }
                }
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            }
        }

        if (Collection.class.isAssignableFrom(o.getClass())) {
            LinkedList<Object> list = (LinkedList) o;
            addJavaCollection(list, objectName);
        } else {
            if (o.getClass().getFields().length > 0) {
                store.put(newId, new StringObject(objectName, newId, "{}"));
            }

        }

    }

    @Override
    public void addJavaCollection(Collection o, String collectionName) {
        DBID newId = (DBID) generateUniqueOID();
        ComplexObject cplxObj = new ComplexObject(collectionName, newId, null);
        // System.out.println("putting new complex " + collectionName);
        for (Object obj : o) {
            for (Field f : obj.getClass().getFields()) {
                try {
                    addJavaObject(f.get(obj), f.getName());
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        store.put(newId, cplxObj);
    }

    public void addJavaCollectionV2(Collection o, String collectionName,
            DBID oParentId) {

        //System.out.println("putting new complex " + o);
        for (Object obj : o) {

            DBID newId = (DBID) generateUniqueOID();
            //System.out.println("got new ID = " + newId.getOID());
            ComplexObject cplxObj = new ComplexObject(collectionName, newId,
                    null);
            store.put(newId, cplxObj);

            for (Field f : obj.getClass().getFields()) {
                //System.out.println("\t parent will be : " + newId.getOID()+ " : adding field f  " + f.toString());
                try {
                    addJavaObjectV2(f.get(obj), f.getName(), newId);
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

            if (oParentId != null) {
                //System.out.println("retreiving = " + oParentId.getOID()+ " and adding [" + collectionName + "] with id "+ newId.getOID() + " to it");
                ((ComplexObject) retrieve(oParentId)).add(newId);
            }
        }

    }

    public void addJavaObjectV2(Object o, String objectName, DBID oParentId) {
        Class c = o.getClass();
        DBID newId = null;
        boolean bNeedToAdded = false;
        System.out.println("Jestem tu");
        if (isSimpleType(o)) {
            newId = (DBID) generateUniqueOID();
            bNeedToAdded = true;
            boolean bTryWithSuccess = false;

            try {
                if (!bTryWithSuccess) {
                    store.put(newId, new IntegerObject(objectName, newId, new Integer((String) o)));
                    bTryWithSuccess = true;
                }
            } catch (Exception ex) {
            }

            try {
                if (!bTryWithSuccess) {
                    store.put(newId, new DoubleObject(objectName, newId, new Double((String) o)));
                    bTryWithSuccess = true;
                }
            } catch (Exception ex) {
            }

            try {
                if (!bTryWithSuccess) {
                    String tmp = (String) o;
                    if (tmp.toLowerCase().equals("false") || tmp.toLowerCase().equals("true")) {
                        store.put(newId, new BooleanObject(objectName, newId, new Boolean((String) o)));
                        bTryWithSuccess = true;
                    } else {
                        store.put(newId, new StringObject(objectName, newId, (String) o));
                        bTryWithSuccess = true;
                    }
                }
            } catch (Exception ex) {
            }

            /*if (c == Integer.class || c == int.class) {
             store.put(newId, new IntegerObject(objectName, newId, (Integer) o));
             } else if (c == String.class) {
             store.put(newId, new StringObject(objectName, newId, (String) o));
             bNeedToAdded = true;
             } else if (c == Double.class || c == double.class) {
             store.put(newId, new DoubleObject(objectName, newId, (Double) o));
             bNeedToAdded = true;
             } else if (c == Boolean.class || c == boolean.class) {
             store.put(newId, new BooleanObject(objectName, newId, (Boolean) o));
             bNeedToAdded = true;
             }*/
        } else {
            //System.out.println(objectName + " is not a very simple object: "+ c);
            if (Collection.class.isAssignableFrom(o.getClass())) {
                //System.out.println(o.getClass());
                LinkedList<Object> list = (LinkedList) o;
                addJavaCollectionV2(list, objectName, oParentId);
            } else {
                bNeedToAdded = true;
                newId = (DBID) generateUniqueOID();
                ComplexObject cplxObj = new ComplexObject(objectName, newId,
                        null);
                store.put(newId, cplxObj);

                // iterate over fields
                for (Field f : o.getClass().getFields()) {
                    //System.out.println("\t" + objectName + " has field: " + f.getType());
                    try {
                        if (isSimpleType(f.get(o))) {
                            addJavaObjectV2(f.get(o), f.getName(), newId);
                            //System.out.println(" \t it is");
                        } else {
                            //System.out.println(" \t it is not");
                            // cannot be instanceof - because we do not know
                            // class type yet.. it will be binded dynamicaly
                            if (Collection.class.isAssignableFrom(f.getType())) {
                                LinkedList<Object> list = (LinkedList) f.get(o);
                                addJavaCollectionV2(list, f.getName(), newId);
                            }
                        }
                    } catch (IllegalArgumentException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (SecurityException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        if (bNeedToAdded) {
            if (oParentId != null) {
                ((ComplexObject) retrieve(oParentId)).add(newId);
            } else {
                if (this.getEntryOID() != null) {
                    ((ComplexObject) retrieve(this.getEntryOID())).add(newId);
                }
            }
        }
    }

    public void printDBState() {
        for (Entry<DBID, SBAObject> entry : store.entrySet()) {

            DBID i = (DBID) entry.getKey();
            SBAObject o = entry.getValue();
            String name = o.getName();

            if (o instanceof SimpleObject) {
                System.out.println("<i" + i.getOID() + ", " + name + ", "
                        + o.getObj() + ">(" + (o.getClass().getSimpleName()) + ")");
            } else {
                String childStr = "";
                ComplexObject c = (ComplexObject) o;
                int tmp = 0;
                for (IOID innerI : c.getChildOIDs()) {
                    if (tmp > 0) {
                        childStr += ", ";
                    }
                    childStr += "i" + ((DBID) innerI).getOID();
                    ++tmp;
                }
                System.out.println("<i" + i.getOID() + ", " + name + ", {"
                        + childStr + "}>");
            }

        }

    }

}
